nRF5340学习笔记(二)设备树的初步学习

您所在的位置:网站首页 visio 序列图 双冒号 nRF5340学习笔记(二)设备树的初步学习

nRF5340学习笔记(二)设备树的初步学习

2023-05-26 04:34| 来源: 网络整理| 查看: 265

一、参考资料与链接

    前片文章中有一个博客链接,讲述Zephyr设备树与驱动模型,对应在B站,也有官方的详解视频。视频也大致讲述了Device Tree(设备树)的语法与结构、如何用DeviceTree配置硬件信息、以及C代码访问DeviceTree和Zephyr Driver实现方式。视频链接如下,建议详细看完博客后再看一遍视频,有些不懂得地方就有思路了。

视频连接:详解Zephyr设备树与设备驱动模型

     另外我们也可以通过nordic官网找到nRF Connect SDK的专栏资料。其对应的Zephyr Project栏目下,就有一章专门讲述DeviceTree的文档指导。文档链接地址如下。

https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/build/dts/index.html

下面,我们根据博客内容,参考北欧开发者学院的视频指导,学习设备树的内容,主要是其API、文档结构与驱动模型。以此为基础,在上一个点灯工程内,新增一个button的应用。

二、NCS下板级配置    2.1:Kconfig与DeviceTree

     回到上一篇文章里中,打开文中《开发你的第一个nRF Connect SDK(NCS)/Zephyr应用程序》的链接,跳转到第4章节内容:--nRF Connect SDK项目配置和选项--。

    该章节内容讲述到,NCS中主要包含两种配置项:Kconfig和DeviceTree,其中。

Kconfig主要负责软件配置,生成头文件为autoconf.h

DeviceTree负责硬件配置,生成头文件为devicetree_generated.h

无论是autoconf.h还是devicetree_generated.h,我们在生成的点灯工程中打开这两个文件。如下图所示。

工程目录下文件配置定义

    在NCS中,autoconf.h/devicetree_generated.h是由Python脚本自动生成的,所以作为使用者是无法直接修改的。我们只能去修改这两个头文件的输入。

    2.2:Kconfig生成文件

    autoconf.h文件生成的流程图如下图所示

autoconf.h生成流程图

    autoconf.h还是有许许多多的Kconfig文件生成的(注:其实Kconfig来源于Linux系统,NCS或者Zephyr对其进行了继承和定制),每个模块都有自己的Kconfig文件。如下图,我们可以在文档中给出地址,找到各个模块Kconfig所存放的文件夹地址。

Kconfig source

除了系统模块定义好的Kconfig,我们本身项目模块也可以自定义Kconfig文件,模仿现有文件,在文本编辑器里编写。

    因为Kconfig文件都是SDK中提供好的模块,每个模块都有预定的一个默认值,如果想修改这个默认值,像在source里面一个一个找到地址,再去修改吗岂不是非常繁琐。为此,NCS引入prj.conf文件。我们打开点灯程序中的prj。

prj.conf文件

    因为只是使用了一个GPIO口,所以配置内容比较稀疏。我们通过pri.conf,也可以修改默认的Kconfig选项,而且这个是永久更新,且只适应于本项目。

    Kconfig与我们设置的prj.conf结合后,会先生成一个.conf文件,在生成autoconfig.h。

.config文件

    .config文件格式更加接近Kconfig和prj.conf,相当于一个中间桥梁。我们在VScode的图形插件里面修改Kconfig其实操作对象就是.config文件,.config是一个临时文件,编译系统默认会以它为基准生成autoconfig.h。所以一旦Kconfig或者prj.conf更新了,必须重新编译以生成的autoconfig.h文件。

    2.3 :Device Tree生成文件

        devicetree_generated.h(老版本为devicetree_unfixed.h)生成流程如下图。

devicetree_generated.h生成流程图

    DeviceTree是Linux里面的概念,在DeviceTree中,每一种硬件都可以用DeviceTree语言进行描述,无论是芯片,元器件,包括内含另一种板子。DeviceTree使用树形结构,按照层级关系描述板子中包含的组件。

    每一块板子都会定义一个dts文件。我们使用的开发板是nRF5340DK,例程我们跑在应用核的安全空间,回到我们下载SDK中,可以找到对应的dts文件。

SDK中5340的.dts

        除了板级的.dts,devicetree_generated还有一个输入来源,即本项目的硬件配置文件,overlay文件。我们如果想要求改自己项目下的配置,比如开一个uart、iic、spi,只需要在该项目下定义一个.overlay文件即可。

        与prj.conf一样,通过.overlay修改的板子的配置,是永久的,且只适应于本项目。随后通过.dts和.overlay结合生成一个zephyr.dts文件,最后在生成devicetree_generated.h。

zephyr.dts三、设备树的基础知识

        第二节我们讲述了NCS下,最核心的配置文件的两个功能,Kconfig和DeviceTree。Kconfig有点类似我们nRF52 SDK中的config.h,用于软件配置是否开启外设驱动。而devicetree是处理硬件部分,为外设的引导配置,设置外设中断,属性等功能。

        进一步了解devicetree,首先其是一个树状结构,树状的结构层次决定方式如下

        首先看总线的主从关系、其次看硬件的包含关系

        3.1 DeviceTree的语法

    设备树是一种树状结构。此树的用户可读文本格式为.dts

    (1)DTS基本示例

     1. /dts -v1/; 指明了设备树语法的版本

     2.设备树具有唯一的根节点,节点之间的包含关系使用大括号来确定

     3.节点名称写在大括号之前,节点的属性写在大括号内;属性是键值对(Key-Value pair)的形式。

     4.可以给节点写一个标签(label),便于在其他地方引用

ex:指明一个节点,需要完整的绝对路径,

例如:/a-node/a-sub-bode

ex:也可以直接使用标签来指明,

例如:subnode_label

(2)DeviceTree中节点名称

                                         name@adress

 .name:必须以字母开头。长度1-31字节,允许数字、大小写字母、英文逗号、小数点、加减号、下划线

 @address:节点若果有reg属性,则address必须和reg属性的第一个寄存器地址值相等。可以理解为总线上的地址。如果没有reg属性,则@address必须省略。 address是十六进制

ex:

注意1:英文逗号、小数点、加减号都可以使用,但是在C语言访问需要统一下划线

(3)DeviceTree中属性类型

属性类型表

    注意说明

    phandle:是用来指向其他节点的属性,形成数组即phandles。

    phandle-array:就是一个节点,后面可以带任何信息

(4)DeviceTree文件引用

   1. dts可以引用其他dts或dtsi,这样板卡级dts可以引用芯片级dits,减少编写工作量

点灯工程引用了应用核common与芯片的dtsi

    2.dts也可以引用C语言头文件,从而可以使用里面的枚举值和宏定义

        3.2  用DeviceTree配置硬件信息

    有3.1我们可以了解到,DeviceTree本身的结构和语法比较简单,只是规定一种形式,和硬件的配置没有任何关系。DeviceTree的基本单元是节点(node),节点具有一个名称和多条属性。可以给节点增加标签(label),来便于引用这个节点。

    板卡级别.dts文件可以引用芯片及dtsi,也可以引用.h头文件,从而定义枚举和宏定义。如果用户想自己修改配置,可以在工程里通过写.overlay的方式覆写。

    至于导出文件,第一章节有提到,Zephyr Build System会通过python和各种脚本,会合并所有的dts以及overlay,最终生成zephyr.dts,并导出devicetree_generated.h头文件用于编译。

    对于DeviceTree配置硬件,需要了解一些常见属性

(1)reg、#address-cells与#size-cells

        reg属性:代表此节点在总线上占用的地址和范围。是由多对(address,length)组合而成的。

        #address-cells和#size-cells则表示这个总线上的节点的reg属性里,每个address和size要占用多少个uint32单元。如下代码所示

    也就是说,每个节点的address-cell和size-cell是用来管理子节点地址的寄存器的。

(2)ranges

            当一个节点定义了ranges属性,那么他的子节点就可以使用相对地址,而非绝对地址。如下图所示,peripheral基地址为0x40000000。

            ADC的地址从0xe000开始,是一个相对地址

            ADC在RAM地址空间绝对地址为0x4000e000

            ranges的属性格式为

            

            子空间首地址为0时,子节点的地址就是相对地址

            这三个元素要占用几个uint32单元,由图中同色*-cells决定

带有rangs子节点定义

 *(3)status

        status是用来制定是否启用一个设备(节点),根据DeviceTree Spec有几个选项,但是我们实际在Zephyr中基本只会用到以下两个

        "okay" : 设备是可以操作

        “disable” :设备目前是不可操作的

 ***(4)compatible

        compatible用来说明此节点设备的兼容性,他的值是一个字符串或一个字符串数组。Zephyr构建系统就是用它为每个节点找到合适的驱动程序

        compatible的每个值通常命名规则:vendor,device (供应商,供应商产品)如下

       但是也不做强制要求,可以用户自定义如下

           如果compatible有多个值,zephyr会按顺序寻找驱动

        3.3 域Domain

    DeviceTree是基于总线地址的层次结构(树状结构)

    实际硬件是网状结构,所以DeviceTree如果需要描述实际情况,在本身基于地址的数之外,逻辑上还有一些其他数也可以用DeviceTree描述,eg:GPIO数、中断树、ADC树等等。

这种附加在DeviceTree上,逻辑上产生的树称之为域(Domain)

    每个域都有一个自己的根节点,该根节点称之为控制器(controller),控制器控制了整个域的相关硬件

    (1)域的控制器和子节点

    控制器节点通常通常会有一个布尔类型属性*-controller,来表示自己是某个域的控制器

    而域中的子节点,就可以使用phandle-array类型的属性,来说明自己属于那个域。此属性的第一个值指向控制器的句柄。后续的值是此节点在这个域中的配置,这条配置称为specifier

     控制器节点中会有一个#*-cells属性来指明specifier的大小,需要占用多少个uint32单元

    如下代码段

gpios = specifier

    中断域、adc域、pwm域不做解释,后续用到了在把代码贴出来详细注释

    初学者(我)只需要记住,【specifier是用来写配置的】即可

    (2):Device Binding

    DeviceTree规则:Device Binding

    binding文件是yaml格式文件,由多组键值对组成。每个值可以是:

纯量:单个不可分割的值

对象:把键值对当成值

数组:一组同类型的值

简易语法:

键、值之间用冒号+空格分隔

yaml的层级关系只看缩进(类似python),相同层级缩进必须相同

数组元素可以是纯量、对象。对象的成员也可以有数组(和json非常相似)

    Device Binding作用:

        binding和device tree中的节点,通过conmpatible属性实现联动。

        device tree中节点的属性,必须严格按照binding文件中的要求。

        不同厂商都会针对每个模块写好binding文件

        作为用户也可以自定binding文件

     (3):特殊节点

    /chosen:zephyr kernel选择特定设备

    /aliases:给节点起一个别名,类似label。区别是label任是节点,aliases只是属性名

    /pinctrl:直属根节点,用于管理数字IO的附庸

    /zephyr,user:方便用户开的节点,可以直接在里面写spcifier、配置项等,免去自定义device和写binding的麻烦 

        3.4 总结

DeviceTree本身语法只是提供了一个基于总线主从关系的树形层次结构,此外每个节点可以用属性来存储信息。语法本身并没有规定硬件要如何描述

DeviceTree中的一些常见属性,补充了这方面的空缺

reg、rangs、#address-cells、#size-cells这四个属性描述了总线上的地址分配

status描述了设备是否使能

compatible属性描述了设备的兼容

 在DeviceTree中,除本身的树形结构以外,还具有一些逻辑上的树形结构,称为域。域具有控制器和设备节点,控制器用来实现域的功能的硬件外设,设备节点为了开发方便解耦而进行的一种抽象

实际限制device tree中属性如何写的是device binding文件。binding文件是芯片厂商提供的。有了binding文件,就可以在VS code中实现自动的检查和补全。

zephyr中会有一些特殊的虚拟节点为开发提供便利(eg:chosen、aliase、pinctrl、zephyr user)

四、在C代码中访问Device Tree     4.1 获取节点ID

    DeviceTree最终会用来生成devicetree_generated.h头文件,包含了DeviceTree中的所有信息,在C文件中访问这些信息,需要使用一套宏函数来将其读出。

    首先操作的C文件中包含其头文件   

    DeviceTree的一切信息都包含在属性之中。获取属性先获取节点ID做句柄。获取节点ID方式很多eg:

其他方式参考:https://docs.zephyrproject.org/latest/build/dts/api-usage.html

     4.2 获取属性

  利用DeviceTree API,输入节点id和属性名称,就可以获得属性

     检查属性是否存在: node id 和小写、下划线命名的属性名称

    获取普通属性:DT_PROP(node_id)

    获取reg属性: 1.获取reg blocks数量:DT_NUM_REGS(node_id)

                            2.只有1个block,读取地址和长度如下

               DT_REG_ADDR(node_id)         DT_REG_SIZE(node_id)

                            3.有多个block,需要通过下标索引

               DT_REG_ADDR(node_id,idx)         DT_REG_SIZE(node_id,idx)

                            4.读取interrupts属性       

                获取interrupt specifier数量:DT_NUM_IRQS(node_id)

                获取interrupt specifier:通过node id,下标和val来访问中断配置

     4.3 遍历宏

        DeviceTree API都是宏,也就是定义好的常亮,不能再代码中使用循环语句调用(for while),DeviceTree API提供了遍历宏展开宏

        . 对设备树中的每一个节点都调用宏函数fn

                    DT_FOREACH_NODE(fn)

        . 对设备树中每一个status为okay的节点调用宏函数fn

                    DT_FOREACH_STATUS_OKAY_NODE(fn)

        . 对一个节点的所有子节点遍历调用宏函数fn

                    DT_FOREACH_CHILD(node_id,fn)

更多参考:https://docs.zephyrproject.org/latest/build/dts/api/api.html

    

     4.4 specifier硬件支持

DeviceTree API中还有很多硬件支持的宏,可以直接读取specifier等

参考:    https://docs.zephyrproject.org/latest/build/dts/api/api.html

               https://docs.zephyrproject.org/latest/hardware/index.html

五、Zephyr Driver的实现方式

        尚未完全理解,后面使用例程的时候理解了再做更新

    2023/05/14



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3